package com.maniu.butterknife_compile;

import com.google.auto.service.AutoService;
import com.maniu.butterknife_anno.BindClick;
import com.maniu.butterknife_anno.BindView;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.JavaFile;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;

/**
 * @author suliang
 * @time 2022/9/22 10:35
 * @describe : 注解处理器
 */
@AutoService(Processor.class)
public class ButterKnifeProcessor extends AbstractProcessor {

    private Filer filer;
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        filer = processingEnv.getFiler();
        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,"11111111111111111111_java");
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,"21111111111111111111_java");
        return processingEnv.getSourceVersion();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,"31111111111111111111_java");
        Set<String> annotations = new HashSet<>();
        annotations.add(BindView.class.getCanonicalName());
        annotations.add(BindClick.class.getCanonicalName());
        return annotations;
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,"41111111111111111111_java");
        //获取注解
        Map<TypeElement, ElementForType> typeMap = findAndParseForType(roundEnvironment);
        //根据注解，创建文件

        createNewBindFile(typeMap);

        return false;
    }

    private void createNewBindFile(Map<TypeElement, ElementForType> typeMap)  {
        typeMap.forEach(new BiConsumer<TypeElement, ElementForType>() {
            @Override
            public void accept(TypeElement typeElement, ElementForType elementForType) {
                String className = typeElement.getSimpleName().toString();
                String packageName = processingEnv.getElementUtils().getPackageOf(typeElement).getQualifiedName().toString();
                String newClassName = className + "_java_binder";

                processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,"className = " + className);
                processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,"packageName = " + packageName);
                String target = "target";

                StringBuffer buffer = new StringBuffer();
                buffer.append("package ").append(packageName).append(";\n");
                buffer.append("import android.view.View;\n");
                buffer.append("public class ").append(newClassName) .append( "{\n");

                buffer.append("public ").append(newClassName) .append("(").append(packageName).append(".").append(className).append(" ").append(target).append("){\n");

                elementForType.getViewList().forEach(new Consumer<VariableElement>() {
                    @Override
                    public void accept(VariableElement variableElement) {
                        String simpleName = variableElement.getSimpleName().toString();
                        int resId = variableElement.getAnnotation(BindView.class).value();
                        TypeMirror type = variableElement.asType();
                        buffer.append(target).append(".").append(simpleName) .append("=(").append(type).append(")").append(target).append(".findViewById(").append(resId).append(");\n");
                    }
                });
                elementForType.getClickList().forEach(new Consumer<ExecutableElement>() {
                    @Override
                    public void accept(ExecutableElement executableElement) {
                        String methodName = executableElement.getSimpleName().toString();
                        int[] resId = executableElement.getAnnotation(BindClick.class).value();
                        for(int i = 0 ; i < resId.length ;i++){
                            buffer.append(target).append(".findViewById(").append(resId[0]).append(").setOnClickListener(new View.OnClickListener(){\n")
                                    .append("public void onClick(View v){\n")
                                    .append(target).append(".").append(methodName).append("(v);\n").append("}\n});");
                        }
                    }
                });
                buffer.append("}");
                buffer.append("}");

                try {
                    JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + newClassName);
                    Writer writer = sourceFile.openWriter();
                    writer.write(buffer.toString());
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        });
    }

    private Map<TypeElement, ElementForType> findAndParseForType(RoundEnvironment roundEnvironment) {
        Map<TypeElement,ElementForType> result = new HashMap<>();
        Set<? extends Element> variableElements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
        Set<? extends Element> executableElements = roundEnvironment.getElementsAnnotatedWith(BindClick.class);

        variableElements.forEach((Consumer<Element>) element -> {
            VariableElement ve = (VariableElement) element;
            TypeElement typeElement = (TypeElement) ve.getEnclosingElement();
            ElementForType elementForType ;
            if (result.containsKey(typeElement)){
                elementForType = result.get(typeElement);
            }else{
                elementForType = new ElementForType();
                result.put(typeElement,elementForType);
            }
            List<VariableElement> viewList = elementForType.getViewList();
            if (viewList == null){
                viewList = new ArrayList<>();
                elementForType.setViewList(viewList);
            }
            viewList.add(ve);
        });

        executableElements.forEach((Consumer<Element>) element -> {
            ExecutableElement ee = (ExecutableElement) element;
            TypeElement typeElement = (TypeElement) ee.getEnclosingElement();
            ElementForType elementForType ;
            if (result.containsKey(typeElement)){
                elementForType = result.get(typeElement);
            }else{
                elementForType = new ElementForType();
                result.put(typeElement,elementForType);
            }
            List<ExecutableElement> clickList = elementForType.getClickList();
            if (clickList == null){
                clickList = new ArrayList<>();
                elementForType.setClickList(clickList);
            }
            clickList.add(ee);
        });
        return result;
    }
}